/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package merge_test

import (
	"testing"

	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
	. "sigs.k8s.io/structured-merge-diff/v4/internal/fixture"
	"sigs.k8s.io/structured-merge-diff/v4/typed"
)

var nestedTypeParser = func() Parser {
	parser, err := typed.NewParser(`types:
- name: type
  map:
    fields:
      - name: listOfLists
        type:
          namedType: listOfLists
      - name: listOfMaps
        type:
          namedType: listOfMaps
      - name: mapOfLists
        type:
          namedType: mapOfLists
      - name: mapOfMaps
        type:
          namedType: mapOfMaps
      - name: mapOfMapsRecursive
        type:
          namedType: mapOfMapsRecursive
      - name: struct
        type:
          namedType: struct
- name: struct
  map:
    fields:
    - name: name
      type:
        scalar: string
    - name: value
      type:
        scalar: numeric
- name: listOfLists
  list:
    elementType:
      map:
        fields:
        - name: name
          type:
            scalar: string
        - name: value
          type:
            namedType: list
    elementRelationship: associative
    keys:
    - name
- name: list
  list:
    elementType:
      scalar: string
    elementRelationship: associative
- name: listOfMaps
  list:
    elementType:
      map:
        fields:
        - name: name
          type:
            scalar: string
        - name: value
          type:
            namedType: map
    elementRelationship: associative
    keys:
    - name
- name: map
  map:
    elementType:
      scalar: string
    elementRelationship: associative
- name: mapOfLists
  map:
    elementType:
      namedType: list
    elementRelationship: associative
- name: mapOfMaps
  map:
    elementType:
      namedType: map
    elementRelationship: associative
- name: mapOfMapsRecursive
  map:
    elementType:
      namedType: mapOfMapsRecursive
    elementRelationship: associative
`)
	if err != nil {
		panic(err)
	}
	return SameVersionParser{T: parser.Type("type")}
}()

func TestUpdateNestedType(t *testing.T) {
	tests := map[string]TestCase{
		"listOfLists_change_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						listOfLists:
						- name: a
						  value:
						  - b
						  - c
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						listOfLists:
						- name: a
						  value:
						  - a
						  - c
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				listOfLists:
				- name: a
				  value:
				  - a
				  - c
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("listOfLists", _KBF("name", "a")),
						_P("listOfLists", _KBF("name", "a"), "name"),
						_P("listOfLists", _KBF("name", "a"), "value", _V("a")),
						_P("listOfLists", _KBF("name", "a"), "value", _V("c")),
					),
					"v1",
					false,
				),
			},
		},
		"listOfLists_change_key_and_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						listOfLists:
						- name: a
						  value:
						  - b
						  - c
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						listOfLists:
						- name: b
						  value:
						  - a
						  - c
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				listOfLists:
				- name: b
				  value:
				  - a
				  - c
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("listOfLists", _KBF("name", "b")),
						_P("listOfLists", _KBF("name", "b"), "name"),
						_P("listOfLists", _KBF("name", "b"), "value", _V("a")),
						_P("listOfLists", _KBF("name", "b"), "value", _V("c")),
					),
					"v1",
					false,
				),
			},
		},
		"listOfMaps_change_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						listOfMaps:
						- name: a
						  value:
						    b: "x"
						    c: "y"
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						listOfMaps:
						- name: a
						  value:
						    a: "x"
						    c: "z"
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				listOfMaps:
				- name: a
				  value:
				    a: "x"
				    c: "z"
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("listOfMaps", _KBF("name", "a")),
						_P("listOfMaps", _KBF("name", "a"), "name"),
						_P("listOfMaps", _KBF("name", "a"), "value", "a"),
						_P("listOfMaps", _KBF("name", "a"), "value", "c"),
					),
					"v1",
					true,
				),
			},
		},
		"listOfMaps_change_key_and_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						listOfMaps:
						- name: a
						  value:
						    b: "x"
						    c: "y"
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						listOfMaps:
						- name: b
						  value:
						    a: "x"
						    c: "z"
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				listOfMaps:
				- name: b
				  value:
				    a: "x"
				    c: "z"
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("listOfMaps", _KBF("name", "b")),
						_P("listOfMaps", _KBF("name", "b"), "name"),
						_P("listOfMaps", _KBF("name", "b"), "value", "a"),
						_P("listOfMaps", _KBF("name", "b"), "value", "c"),
					),
					"v1",
					false,
				),
			},
		},
		"mapOfLists_change_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						mapOfLists:
						  a:
						  - b
						  - c
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						mapOfLists:
						  a:
						  - a
						  - c
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				mapOfLists:
				  a:
				  - a
				  - c
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("mapOfLists", "a"),
						_P("mapOfLists", "a", _V("a")),
						_P("mapOfLists", "a", _V("c")),
					),
					"v1",
					true,
				),
			},
		},
		"mapOfLists_change_key_and_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						mapOfLists:
						  a:
						  - b
						  - c
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						mapOfLists:
						  b:
						  - a
						  - c
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				mapOfLists:
				  b:
				  - a
				  - c
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("mapOfLists", "b"),
						_P("mapOfLists", "b", _V("a")),
						_P("mapOfLists", "b", _V("c")),
					),
					"v1",
					false,
				),
			},
		},
		"mapOfMaps_change_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						mapOfMaps:
						  a:
						    b: "x"
						    c: "y"
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						mapOfMaps:
						  a:
						    a: "x"
						    c: "z"
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				mapOfMaps:
				  a:
				    a: "x"
				    c: "z"
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("mapOfMaps", "a"),
						_P("mapOfMaps", "a", "a"),
						_P("mapOfMaps", "a", "c"),
					),
					"v1",
					false,
				),
			},
		},
		"mapOfMaps_change_key_and_value": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						mapOfMaps:
						  a:
						    b: "x"
						    c: "y"
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						mapOfMaps:
						  b:
						    a: "x"
						    c: "z"
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				mapOfMaps:
				  b:
				    a: "x"
				    c: "z"
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("mapOfMaps", "b"),
						_P("mapOfMaps", "b", "a"),
						_P("mapOfMaps", "b", "c"),
					),
					"v1",
					false,
				),
			},
		},
		"mapOfMapsRecursive_change_middle_key": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						mapOfMapsRecursive:
						  a:
						    b:
						      c:
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						mapOfMapsRecursive:
						  a:
						    d:
						      c:
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				mapOfMapsRecursive:
				  a:
				    d:
				      c:
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("mapOfMapsRecursive", "a"),
						_P("mapOfMapsRecursive", "a", "d"),
						_P("mapOfMapsRecursive", "a", "d", "c"),
					),
					"v1",
					false,
				),
			},
		},
		"struct_apply_remove_all": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
						  value: 1
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
					`,
					APIVersion: "v1",
				},
			},
			Object: `
			`,
			APIVersion: "v1",
			Managed:    fieldpath.ManagedFields{},
		},
		"struct_apply_remove_dangling": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						struct:
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				struct:
			`,
			APIVersion: "v1",
			Managed: fieldpath.ManagedFields{
				"default": fieldpath.NewVersionedSet(
					_NS(
						_P("struct"),
					),
					"v1",
					true,
				),
			},
		},
		"struct_apply_update_remove_all": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "controller",
					Object: `
						struct:
						  name: a
						  value: 1
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				struct:
				  value: 1
			`,
			APIVersion: "v1",
		},
		"struct_apply_update_dict_dangling": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "controller",
					Object: `
						struct:
						  name: a
						  value: 1
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						struct: {}
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				struct:
				  value: 1
			`,
			APIVersion: "v1",
		},
		"struct_apply_update_dict_null": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "controller",
					Object: `
						struct:
						  name: a
						  value: 1
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						struct:
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				struct:
				  value: 1
			`,
			APIVersion: "v1",
		},
		"struct_apply_update_took_over": {
			Ops: []Operation{
				Apply{
					Manager: "default",
					Object: `
						struct:
						  name: a
					`,
					APIVersion: "v1",
				},
				Update{
					Manager: "controller",
					Object: `
						struct:
						  name: b
						  value: 1
					`,
					APIVersion: "v1",
				},
				Apply{
					Manager: "default",
					Object: `
						struct:
					`,
					APIVersion: "v1",
				},
			},
			Object: `
				struct:
				  name: b
				  value: 1
			`,
			APIVersion: "v1",
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			if err := test.Test(nestedTypeParser); err != nil {
				t.Fatal(err)
			}
		})
	}
}
